package cc.noau.coder.generator;

import cc.noau.coder.bean.CoderSetting;
import cc.noau.coder.bean.SettingRow;
import cc.noau.coder.bean.Table;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * <h1> 代码生成器抽象类 </h1>
 * <p>
 * <b>描述：</b>
 * 实现一些基础方法
 * </p>
 * @author lxz
 */
public abstract class AbstractCodeGenerator implements CodeGenerator {


    /**
     * 把生成好的代码写入到文件中，如果文件不存在，则会创建，否则会进行覆盖。
     *
     * @param filePath   输出路径
     * @param fileName   文件名
     * @param sourceCode 代码
     */
    void writeToFile(String filePath, String fileName, String sourceCode) {
        File dir = new File(filePath);
        if (dir.mkdirs()) {
            System.out.println("create dirs " + filePath);
        }
        String fileNameFull = filePath + "/" + fileName;//文件路径+名称+文件类型
        File file = new File(fileNameFull);
        try {
            //如果文件不存在，则创建新的文件
            if (!file.exists()) {
                file.createNewFile();
                System.out.println("success create file,the file is " + fileNameFull);
            }else{
                System.out.println("the file " + fileNameFull + " is exits.");
            }
            //创建文件成功后，写入内容到文件里
            writeFileContent(fileNameFull, sourceCode);
        } catch (Exception e) {
            System.out.println(fileNameFull);
            e.printStackTrace();
        }

    }


    /**
     * 读取文件中的需要保留的内容。对于需要保留的内容，下一次重新生成代码
     * 时，会被保留下来。需要保留的内容是文件中 被 #marking start，
     * #marking end 包裹的代码。
     *
     * @param filePath 需要读取保留内容的文件所在的目录
     * @param fileName 需要读取保留内容的文件名
     * @return 需要被保留下来的内容
     */
    StringBuilder readRetainedContent(String filePath, String fileName) {
        StringBuilder result = new StringBuilder("");
        File dir = new File(filePath);
        if (!dir.exists()) {
            return result; // 如果目录不存在，则直接返回
        }
        String fileNameFull = filePath + "/" + fileName;//文件路径+名称+文件类型
        File file = new File(fileNameFull);

        if (file != null && file.exists()) {   // 如果文件存在，则读取需要保留的内容

            String text = readFileByLines(fileNameFull);

            if ("".equals(text)) {
                return result;
            }

            BufferedReader reader = new BufferedReader(new StringReader(text));
            String line;
            boolean read = false;
            try {
                while ((line = reader.readLine()) != null) {
                    if (line.contains("#marking start")) // 需要保留的内容，起始标记
                    {
                        read = true;
                        continue;
                    }
                    if (line.contains("#marking end")) // 需要保留的内容，结束标记
                    {
                        read = false;
                    }
                    if (read) {
                        if (!line.trim().equals(""))
                            result.append(line).append("\n"); // 添加需要保留的内容到返回结果中
                    }
                }
                if (result.length() > 1)
                    result.deleteCharAt(result.length() - 1); // 删除最后多余的一个换行符
                reader.close();
            } catch (IOException e) {
                throw new RuntimeException("read content which need to be retained throw a exception in file :" + filePath + " " + fileName + "because : " + e.getMessage());
            }
        } else {
            return result;
        }
        return result;
    }

    /**
     * 根据模板和相关参数，生成实际的代码。
     * 模板中定义的相关参数锚点，以 $parameterName$ 的形式存在。
     * 通过此方法可以把所有的锚点替换成对应的参数值。
     * 除此以为要有一种名为 $ScriptEngine $ 的特殊锚点。它表示的
     * 是一段JS代码。通过执行这段代码，来实现一些复杂的模板拼装。
     *
     * @param template   模板
     * @param parameters 参数
     * @return 实际的，最终被输出的代码
     */
    String replace(String template, Map<String, Object> parameters) {
        StringBuilder sourceCode = new StringBuilder();
        int start = -1;
        StringBuilder word = new StringBuilder();
        for (int i = 0; i < template.length(); i++) {
            char c = template.charAt(i);
            if (c == '$') {
                if (start == -1) {
                    start = i;
                } else {
                    String keyWord = word.toString();
                    if (word.toString().startsWith("ScriptEngine")) {
                        // 使用脚本处理
                        sourceCode.append(handleScript(keyWord.substring(12), parameters));
                    } else {
                        Object value; // 赋值处理
                        sourceCode.append((value = parameters.get(keyWord)) == null ? "[the value of parameter \"" + keyWord + "\" is not set]" : value);
                    }
                    word = new StringBuilder();
                    start = -1;
                }
            } else if (start > 0) {
                word.append(c);
            } else {
                sourceCode.append(c);
            }
        }
        return sourceCode.toString();
    }

    /**
     * 根据上下文参数，执行脚本，输出拼装好的字符串
     *
     * @param script     模板中的脚本代码
     * @param parameters 上下文参数
     * @return 拼装好的字符串，如果脚本执行出错，则会输出错误信息
     */
    private StringBuilder handleScript(String script, Map<String, Object> parameters) {
        StringBuilder builder = new StringBuilder();
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("js");
        engine.put("builder", builder);
        engine.put("columns", parameters.get("columns"));
        engine.put("primaryKeys", parameters.get("primaryKeys"));
        engine.put("lowerDomainObjectName", parameters.get("lowerDomainObjectName"));
        engine.put("keyProperty", parameters.get("keyProperty"));
        engine.put("UseGeneratedKeys", parameters.get("UseGeneratedKeys"));
        try {
            engine.eval(script);
        } catch (ScriptException e) {
            builder = new StringBuilder();
            builder.append("ScriptEngine run failed:\n").append(e.getMessage());
        }
        return builder;
    }

    /**
     * 转化出真正的包名
     * 原始包名可能包含有 $module$ 字段，需要取配置参数中的 Module 的值来替换
     * 最后得出有效的包名
     * 需要这么做的原因是在常见的包结构中，包路径上可能会有模块名。这个模块名对于需要
     * 不同一次映射规则来说，可能是不同的。
     *
     * @param packageName 原始包名
     * @param module      用户定义的模块名
     * @return 真正的包名
     */
    String toRealPackageName(String packageName, String module) {
        return packageName.replaceAll("\\$module\\$", module);
    }

    Map<String, Object> generateParamMap(CoderSetting setting, SettingRow settingRow, Table table, StringBuilder retainedCode) {
        Map<String, Object> parameters = new HashMap<>();

        Date currentTime = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");


        parameters.put("javaModelPackage", toRealPackageName(setting.getJavaModelPackage(), settingRow.getModule()));
        parameters.put("javaModelVoPackage", toRealPackageName(setting.getJavaModelVoPackage(), settingRow.getModule()));
        parameters.put("javaInterfacePackage", toRealPackageName(setting.getJavaInterfacePackage(), settingRow.getModule()));
        parameters.put("javaServicePackage",toRealPackageName(setting.getJavaServicePackage(), settingRow.getModule()));
        parameters.put("javaServiceImplPackage",toRealPackageName(setting.getJavaServiceImplPackage(), settingRow.getModule()));
        parameters.put("javaControllerPackage",toRealPackageName(setting.getJavaControllerPackage(), settingRow.getModule()));

        // 实体类名
        parameters.put("DomainObjectName", settingRow.getDomainObjectName());
        // 第一个字母小写实体类名
        parameters.put("lowerDomainObjectName", settingRow.getDomainObjectName().substring(0,1).toLowerCase() + settingRow.getDomainObjectName().substring(1));
        parameters.put("keyProperty", table.getPrimaryKeys().size() > 0 ? table.getPrimaryKeys().get(0).getPropertyName() : "");
        // 表名
        parameters.put("tableName", table.getTableName());
        // 表说明
        parameters.put("tableComment", table.getTableComment());
        parameters.put("columns", table.getColumns());
        parameters.put("UseGeneratedKeys", settingRow.getUseGeneratedKeys());
        parameters.put("primaryKeys", table.getPrimaryKeys());
        parameters.put("retainedCode", retainedCode);

        // 其它
        parameters.put("author",setting.getAuthor());
        parameters.put("version",setting.getVersion());

        parameters.put("createTime", formatter.format(currentTime));

        return parameters;
    }

    /**
     * 向文件中写入内容
     *
     * @param filepath 文件路径与名称(全路径)
     * @param newstr   写入的内容
     * @return
     * @throws IOException
     */
    private static boolean writeFileContent(String filepath, String newstr) throws IOException {
        Boolean bool = false;
        Boolean isRetainOldFileStr = false;
        String filein = newstr + "\r\n";//新写入的行，换行
        String temp = "";

        FileInputStream fis = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        FileOutputStream fos = null;
        PrintWriter pw = null;
        try {
            File file = new File(filepath);//文件路径(包括文件名称)
            StringBuffer buffer = new StringBuffer();

            //是否保留原有内容 (目前不保留)
            if(isRetainOldFileStr) {
                //将文件读入输入流
                fis = new FileInputStream(file);
                isr = new InputStreamReader(fis);
                br = new BufferedReader(isr);

                //文件原有内容
                for (int i = 0; (temp = br.readLine()) != null; i++) {
                    buffer.append(temp);
                    // 行与行之间的分隔符 相当于“\n”
                    buffer = buffer.append(System.getProperty("line.separator"));
                }
            }

            buffer.append(filein);

            fos = new FileOutputStream(file);
            pw = new PrintWriter(fos);
            pw.write(buffer.toString().toCharArray());
            pw.flush();
            bool = true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //不要忘记关闭
            if (pw != null) {
                pw.close();
            }
            if (fos != null) {
                fos.close();
            }
            if (br != null) {
                br.close();
            }
            if (isr != null) {
                isr.close();
            }
            if (fis != null) {
                fis.close();
            }
        }
        return bool;
    }

    /**
     * 读取文件内容->string
     *
     * @param fileNameFull 文件全路径
     * @return
     */
    private static String readFileByLines(String fileNameFull) {
        FileInputStream file = null;
        BufferedReader reader = null;
        InputStreamReader inputFileReader = null;
        String content = "";
        String tempString = null;
        try {
            file = new FileInputStream(fileNameFull);
            inputFileReader = new InputStreamReader(file, "utf-8");
            reader = new BufferedReader(inputFileReader);
            // 一次读入一行，直到读入null为文件结束
            while ((tempString = reader.readLine()) != null) {
                content += tempString;
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e1) {
                }
            }
        }
        return content;
    }

}